//////////////////////////////////////////////////////////////////////////////
//
// File:  conGuy.cc
//
// Description:  This is the implementation file for the conGuy class.
//   This class manages and processes all the necessary actions
//   required to connect to bnet.  (accnt, pwd and auth challenges).
//   It does NOT handle the parsing of the chat packets following the
//   connect completed.
// 
// Status:  Incomplete.
//
// Notes:  
//   1.  variables hardcoded:  
//             exeVer 
//             versionByte
//   
//
#include <iostream>
#include <string>
#include <fstream>
#include <ctime>
#include <sstream>
#include <list>

using namespace std;

#include <qthread.h>
#include <qmessagebox.h>
#include <qhostaddress.h>
#include <qapplication.h>  // For exit().
#include <qfile.h>         // For finding config file.
#include <q3valuelist.h>

// My stuff:
#include "src/conGuy/conGuy.h"
#include "src/conGuy/buff.h"
#include "src/conGuy/types.h"

#include "src/event/BnetEvent.h"



// This is the default maximum recv message size.
#define MAX_MSG 1024  

// This is the number of seconds between the
// last send time and the time a SID_NULL packet will need to be sent.
#define SECONDS_SID_NULL 480 

//extern QApplication *qApp;

string to_s(u32 num);


conGuy::conGuy(ChatDialog *chatDialog)
 {
  settingsDialog = new SettingsDialog(&account, &password, &server,
                                      &home_channel, &cdkey, 
                                      &starcraftVersion_,
                                      &hash1, &hash2, &hash3 );
  chatDialog_ = chatDialog;  // Used to emit logging to.

  // Create the qt socket.
  qtsocket = new Q3SocketDevice;
  // Set socket as blocking.
  qtsocket->setBlocking( true );
  
  // Set pointer to NULL.
  dns = 0;

  // Standard Battle.net port.
  port = 6112;

  done = false;

  // Turn off flood control untill we're logged in.
  enableFloodControl( false );
  
  connect(this, SIGNAL(appendChat(QString, QString)),
	  chatDialog_, SLOT(appendChat(QString, QString)));
}


conGuy::~conGuy() {
    disconnectNow();
    if( dns != 0 )
    {
        delete dns;
    }
    if( qtsocket != 0 )
    {
        delete qtsocket;
    }
}


// Read the config file in and do the essential stuff to connect to bnet.
// This function may only be called once during the lifetime of the bot.
bool conGuy::connectNow() {
    time_t rawtime;
    struct tm * timeinfo;
    time ( &rawtime );
    timeinfo = localtime ( &rawtime );

    // Log timestamp of connection start.
    log(string("GiTMchat loading at:                    ") +
        string(asctime(timeinfo)));
  
    log(string("Gathering configuration information..."));

    if(!read_config()) 
    {
        log("Connect attempt aborted.");
        return false;
    }

    debuglog(string("Doing DNS lookup on ") + server );

    dns = new Q3Dns( server.c_str() );
    connect( dns, SIGNAL(resultsReady()),
             this, SLOT(connectToBnet()) );
  
    if( !dns->isWorking() )
    {
        log("DNS lookup isn't working.");
        return false;
    }

    enableFloodControl();

    return true;
}  // end of bool conGuy::connectNow()



bool conGuy::disconnectNow() {
    qtsocket->close();
    qtsocket->setSocket( -1, Q3SocketDevice::Stream );

  time_t rawtime;
  struct tm * timeinfo;
  time ( &rawtime );
  timeinfo = localtime ( &rawtime );

  log(string("GiTMchat disconnect at:                 ")
      + string(asctime(timeinfo)));
  log("---------------------------------------------------------------");

  // Turn off flood control.  (We'll turn it back on when we're connected).
  enableFloodControl( false );

  return true;
}


bool conGuy::isConnected() {
  if( qtsocket->isValid() )
    return true;
  else 
    return false;
}


// Return the pointer to the toSend sendQueue.  Remember!  The sendQueue
//   has a mutex!  Lock before modifying.  :)
sendQueue* conGuy::getSendQueue() {
  return &toSend;
}


// Return the socket descriptor.  This could be useful I think.
int conGuy::getSd() {
  return sd;
}

string conGuy::homeChannel() {
  return home_channel;
}

string conGuy::username() {
  return unique_username;
}

bool conGuy::getHaveOps() {
  return haveOps;
}

bool conGuy::setHaveOps(bool ops) {
  haveOps = ops;
  return true;
}

void conGuy::run()
{
    // Empty dummy function that is not used.  I inherit QThread only
    // so that i may use the sleep and usleep functions.
}

void conGuy::log(string msg) {
  emit appendChat(QString(msg.c_str()), QString("system"));
}
void conGuy::debuglog(string msg) {
  emit appendChat(QString(msg.c_str()), QString("debug"));
}

// Send bnet a chat message or command.
bool conGuy::say(string msg) {
  return s_pkt_SID_CHATCOMMAND(msg);
}



// This function reads the #defined CONGUY_CONFIG file for the configuration
//   stuff.
bool conGuy::read_config() {

  // Fetch them bones.

  if(!settingsDialog->exec()) {
    log(string("User decided not to log in."));
    return false;
  }

  // If someone enters a username with a space at the beginning or end,
  //   it could get us IP banned.
  istringstream accountStream(account.c_str());
  accountStream >> account;


  
  // Tell authGuy what those values are.
  auth.setAccount(account);
  auth.setPassword(password);
  auth.setCdkey(cdkey);
  auth.setHashfile1(hash1);
  auth.setHashfile2(hash2);
  auth.setHashfile3(hash3);
  
  return true;
} // end of bool conGuy::read_config()



// Reads data from a socket.  msg_len is max message size.  The return value
//   is how many bytes were read.
int conGuy::readit(void* msg_data, unsigned int msg_len)
{
  Q_LONG rc = qtsocket->readBlock( static_cast<char*>(msg_data), msg_len );
  
  if( rc < 0 )
  {
      log(string("ERROR:  Socket read error.  ") + 
          printErrorType( qtsocket->error() ) );
      return -1;
  }
  else if( rc == 0 )
  {
      log("ERROR:  Socket read error.  Remote host closed the connection.");
      return -1;
  }
  else 
  {
      return rc;
  }
} // end of int conGuy::readit()



// Writes data to a socket descriptor or length msg_len.
bool conGuy::writeit(void*msg_data, unsigned int msg_len)
{
  char *buf_ptr;
  Q_LONG sent = 0;

  buf_ptr = (char*)msg_data;
  /* Handle possible interrupt before entire buffer
   * is written out.
   */
  sent = 0;
  while( msg_len > 0 && sent != -1) {

      sent = qtsocket->writeBlock( buf_ptr, msg_len );

    /* Here we handle the possibility of writing less
     * than the entire buffer due to an interrupt.
     */
    if( sent > 0 ) {
      msg_len -= sent;
      buf_ptr += sent;
    }
    else if (sent < 0)  {
      log(string("ERROR:  Socket write error.  ") + 
          printErrorType( qtsocket->error() ) );
      return false;
    }
  }

  // Should have sent everything.
  Q_ASSERT( msg_len == 0 );

  return true;
} // end of bool conGuy::writeit()



u32 conGuy::getExeVer()
{
    // Older hardcoded values from earlier versions.
    //   exeVer = 0x01010101;   // for v 1.11
    //   exeVer = 0x01010103;   //  for v 1.11b
    //   exeVer = 0x00000001;   // for v 1.12  (but then it stopped working...)
    //   exeVer = 0x01010203;   // for v 1.12
    //   exeVer = 0x01010301;   // for v 1.13
    //   exeVer = 0x01010303;   // for v 1.13b
    //   exeVer = 0x01010305;   // for v 1.13c
    //   exeVer = 0x01010307;   // for v 1.13d
    //   exeVer = 0x01010309;   // for v 1.13e
    //   exeVer = 0x0101030b;   // for v 1.13f

    u32 defaultExeVer = 0x0101030b;  // This is what it is for 1.13f.

    if( starcraftVersion_ == "" )
    {
        return defaultExeVer;
    }


    u32 exeVer = 0x00000000;

    QString versionPart1; // Whole number part.
    QString versionPart2; // Tenths part.
    QString versionPart3; // Hundreths part.
    QString versionPart4; // Letter part.

    versionPart1 = starcraftVersion_.substr( 0, 1 ).c_str();
    versionPart2 = starcraftVersion_.substr( 2, 1 ).c_str();
    versionPart3 = starcraftVersion_.substr( 3, 1 ).c_str();
    versionPart4 = starcraftVersion_.substr( 4, 1 ).c_str();
    
    switch( versionPart1.toInt() )
    {
    case 0: exeVer |= 0x00000000; break;
    case 1: exeVer |= 0x01000000; break; 
    case 2: exeVer |= 0x02000000; break; 
    case 3: exeVer |= 0x03000000; break; 
    case 4: exeVer |= 0x04000000; break; 
    case 5: exeVer |= 0x05000000; break;
    case 6: exeVer |= 0x06000000; break;
    case 7: exeVer |= 0x07000000; break; 
    case 8: exeVer |= 0x08000000; break; 
    case 9: exeVer |= 0x09000000; break; 
    }

    switch( versionPart2.toInt() )
    {
    case 0: exeVer |= 0x00000000; break;
    case 1: exeVer |= 0x00010000; break; 
    case 2: exeVer |= 0x00020000; break; 
    case 3: exeVer |= 0x00030000; break; 
    case 4: exeVer |= 0x00040000; break; 
    case 5: exeVer |= 0x00050000; break;
    case 6: exeVer |= 0x00060000; break;
    case 7: exeVer |= 0x00070000; break; 
    case 8: exeVer |= 0x00080000; break; 
    case 9: exeVer |= 0x00090000; break; 
    }

    switch( versionPart3.toInt() )
    {
    case 0: exeVer |= 0x00000000; break;
    case 1: exeVer |= 0x00000100; break; 
    case 2: exeVer |= 0x00000200; break; 
    case 3: exeVer |= 0x00000300; break; 
    case 4: exeVer |= 0x00000400; break; 
    case 5: exeVer |= 0x00000500; break;
    case 6: exeVer |= 0x00000600; break;
    case 7: exeVer |= 0x00000700; break; 
    case 8: exeVer |= 0x00000800; break; 
    case 9: exeVer |= 0x00000900; break; 
    }

    if( versionPart4 == "" )
    {
        exeVer |= 0x00000001;
    }
    else if( versionPart4 == "a" )
    {
        exeVer |= 0x00000001;
    }
    else if( versionPart4 == "b" )
    {
        exeVer |= 0x00000003;
    }
    else if( versionPart4 == "c" )
    {
        exeVer |= 0x00000005;
    }
    else if( versionPart4 == "d" )
    {
        exeVer |= 0x00000007;
    }
    else if( versionPart4 == "e" )
    {
        exeVer |= 0x00000009;
    }
    else if( versionPart4 == "f" )
    {
        exeVer |= 0x0000000b;
    }
    else if( versionPart4 == "g" )
    {
        exeVer |= 0x0000000d;
    }
    else if( versionPart4 == "h" )
    {
        exeVer |= 0x0000000f;
    }
    else if( versionPart4 == "i" )
    {
        exeVer |= 0x00000011;
    }
    else if( versionPart4 == "j" )
    {
        exeVer |= 0x00000013;
    }
    else if( versionPart4 == "k" )
    {
        exeVer |= 0x00000015;
    }
    else
    {
        // Ah, shit.  Default to something...
        exeVer |= 0x00000001;
    }
    
    return exeVer;
}


u32 conGuy::getVersionByte()
{
    // Older hardcoded values from earlier versions.
    //   u32 versionByte = (u32)0xcd; // (u32)0xc9 for version 1.11, 1.11b, 
    //                                // (u32)0xcb for 1.12
    //                                // (u32)0xcd for 1.13, 1.13b, 1.13c...
    
    // Set a default value for versionByte.
    u32 versionByte = 0xcd;  // What it is for 1.13.
    QString mainVersionPart;
    mainVersionPart = starcraftVersion_.substr( 0, 4 ).c_str(); // "1.13".
    
    if( mainVersionPart == "1.13" )
    {
        versionByte = 0xcd;
    }
    else if( mainVersionPart == "1.14" )
    {
        versionByte = 0xcf;
    }
    else if( mainVersionPart == "1.15" )
    {
        versionByte = 0xd1;
    }
    else if( mainVersionPart == "1.16" )
    {
        versionByte = 0xd3;
    }
    else if( mainVersionPart == "1.17" )
    {
        versionByte = 0xd5;
    }
    else if( mainVersionPart == "1.18" )
    {
        versionByte = 0xd7;
    }

    return versionByte;
}



// Enables or disables flood control by resetting the parameters for
// amount of sleep time.
void conGuy::enableFloodControl( const bool& enable )
{
    if( enable == true )
    {
        // Enable flood control.
        sleep_sec_req = 0;         // Minimum required seconds to sleep
        sleep_msec_req = 1200;     // Minimum required milliseconds to sleep.
        sleep_msec_per_byte = 40;  // Number of milliseconds to sleep per byte
                                   // when packet size is > 30 bytes.
    }
    else
    {
        // Disable flood control.
        sleep_sec_req = 0;        // Minimum required seconds to sleep
        sleep_msec_req = 0;       // Minimum required milliseconds to sleep.
        sleep_msec_per_byte = 0;  // Number of milliseconds to sleep per byte
                                  //   when packet size is > 30 bytes.
    }
} // End of     void conGuy::enableFloodControl( const bool& enabled )

// Self explanatory I believe...
bool conGuy::updateLastSendTime() {
  lastSendTime = QDateTime::currentDateTime();
  lastSendSize = sendBuff.size();
  return true;
} // end of bool conGuy::updateLastSendTime()




// Call this before every send.  (sendBuffOut)
// It does a sleep() or usleep() as required.
bool conGuy::sleepRequiredTime() {
  // Flag to check if I need to sleep.
  bool needToSleep = false;
  
  // Current timestamp.
  QDateTime currTime = QDateTime::currentDateTime();

  // Time required to wait.
  long secReq  = sleep_sec_req;
  long msecReq = sleep_msec_req;

  // Time I will sleep:
  long secSleep;
  long msecSleep;

  // Elapsed time.
  long secElapsed;
  long msecElapsed;
  
  // If the packet size was >= 30 bytes, we use 35 milliseconds per byte.
  if(lastSendSize >= 30) {
    secReq  = (lastSendSize * sleep_msec_per_byte) / 1000;
    msecReq = (lastSendSize * sleep_msec_per_byte) % 1000;
  }
  
  // Set elapsed time.
  secElapsed  = currTime.toTime_t() - lastSendTime.toTime_t();
  msecElapsed = currTime.time().msec() - lastSendTime.time().msec();
  // We can't have negative times, can we?  Fix that.
  if(msecElapsed < 0) {
    secElapsed--;
    msecElapsed += 1000;
  }

  if(secElapsed < secReq) {
    needToSleep = true;
  }
  if(secElapsed == secReq && msecElapsed < msecReq) {
    needToSleep = true;
  }

  if(needToSleep == true) {
    secSleep  = secReq  - secElapsed;
    msecSleep = msecReq - msecElapsed;
    // We can't have negative times, can we?  Fix that.
    if(msecSleep < 0) {
      secSleep--;
      msecSleep += 1000;
    }
    if(secSleep  >= 1)
    {
      SleeperThread::sleep(secSleep);
      //      QThread::sleep(secSleep);
    }
    if(msecSleep >= 1)
    {
      SleeperThread::msleep(msecSleep);
      //      QThread::msleep(msecSleep);
    }
  }
  return true;
}  // end of bool conGuy::sleepRequiredTime()




// This function processes and does the essential sends & recvs necessary
//   to log on to bnet.
//
// This is the logon sequence I use:
  // 
  // SEND -> 	Protocol byte	(01)
  // SEND ->	SID_AUTH_INFO	(0x50)
  // RECV <-	SID_PING	(0x25)
  // RECV <-	SID_AUTH_INFO	(0x50)
  // SEND ->	SID_PING	(0x25) [Optional], but I do it.
  // SEND ->	SID_AUTH_CHECK	(0x51)
  // RECV <-	SID_AUTH_CHECK	(0x51)
  // SEND ->	SID_LOGONRESPONSE	(0x29)
  // RECV <-	SID_LOGONRESPONSE	(0x29)
  // SEND ->	SID_UDPPINGRESPONSE	(0x14) [Game Specific: SEXP/STAR/W2BN]
  // SEND ->	SID_ENTERCHAT	(0x0A)
  // SEND ->    SID_JOINCHANNEL (0x0C)
  // RECV <-    SID_ENTERCHAT	(0x0A)

bool conGuy::connectToBnet() {
    disconnect( dns, SIGNAL( resultsReady() ), this, SLOT( connectToBnet() ) );
    Q3ValueList<QHostAddress> addresses = dns->addresses();
    QHostAddress serverAddr = dns->addresses().first();
    
    log( string("Attempting to connect to ") + 
         string( serverAddr.toString().ascii() ) + 
         string( " on port ") + 
         QString::number( port ).ascii() );

    // Connect
    if( !qtsocket->connect( serverAddr, port ) )
    {
        log( string("ERROR:  Attempt to connect to server failed.  ") +
             printErrorType( qtsocket->error() ) );
        disconnectNow();
        log("Connect attempt failed.");
        return false;
    }
    else
    {
        log("Successful connection established to server.");
    }

    // Starting to send stuff for the log on process!!
    // See the top of this function for comments on the procedure I use.
  
    bool login_complete = false;

    log("Proceeding to log into account...");

    // Send PROTOCOL BYTE.  (game protocol)
    if(!s_pkt_protocol()) {
        debuglog(string("ERROR:    Send protocol byte failed.  ")
                 + string("          ") + string( __FILE__ )
                 + string("(") 
                 + string( QString::number(int(__LINE__)).ascii() ) 
                 + string(")"));
        log("Connect attempt failed.");
        disconnectNow();
        return false;
    }
    debuglog("Sent Protocol byte.");

  
    // Send SID_AUTH_INFO.
    if(!s_pkt_SID_AUTH_INFO()) {
        debuglog(string("ERROR:    Send packet SID_AUTH_INFO failed.")
                 + string("          ") + string( __FILE__ )
                 + string("(") 
                 + string( QString::number(int(__LINE__)).ascii() ) 
                 + string(")"));
        log("Connect attempt failed.");
        disconnectNow();
        return false;
    }
    debuglog("Sent SID_AUTH_INFO");



  
    // Now we wait for responses to the stuff we've sent and react accordingly.
    recvBuff.clear();
    recvBuff.setStripped( false );
    while( !login_complete && qtsocket->isValid() ) {
        qApp->processEvents();
        int timeout = 3000; // Milliseconds
        if( !readFullPacket( timeout ) )
        {
            log("Connect attempt failed.");
            qApp->processEvents();
            disconnectNow();
            return false;
        }

        char pktID[16];
        sprintf(pktID, "%d", recvBuff.getPacketID());
        debuglog(string("Processing input packet ") + string(pktID));
        qApp->processEvents();
        switch(recvBuff.getPacketID()) {
      
        case SID_PING: {
            u32 pingVal;
            if(! r_pkt_SID_PING(pingVal)) {
                debuglog(string("ERROR:  r_pkt_SID_PING() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Recv a ping.");
            qApp->processEvents();
            if(! s_pkt_SID_PING(pingVal)) {
                debuglog(string("ERROR:  s_pkt_SID_PING() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Sent a ping");
            qApp->processEvents();
        } break;


        case SID_AUTH_INFO: {
            if(! r_pkt_SID_AUTH_INFO()) {
                debuglog(string("ERROR:  r_pkt_SID_AUTH_INFO() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog(string("Recv SID_AUTH_INFO"));
            qApp->processEvents();
            if(! s_pkt_SID_AUTH_CHECK()) {
                debuglog(string("ERROR:  s_pkt_SID_AUTH_CHECK() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog(string("Sent SID_AUTH_CHECK"));
            qApp->processEvents();
        } break;
    


        case SID_AUTH_CHECK: {
            if(! r_pkt_SID_AUTH_CHECK()) {
                debuglog(string("ERROR:  r_pkt_SID_AUTH_CHECK() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog(string("Recv SID_AUTH_CHECK"));
            if(! s_pkt_SID_LOGONRESPONSE()) {
                debuglog(string("ERROR:  s_pkt_SID_LOGONRESPONSE() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog(string("Sent SID_LOGONRESPONSE"));
            qApp->processEvents();
        } break;


        case SID_LOGONRESPONSE: {
            if(! r_pkt_SID_LOGONRESPONSE()) {
                debuglog(string("ERROR:  r_pkt_SID_LOGONRESPONSE() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Recv SID_LOGONRESPONSE");

            if(! s_pkt_SID_UDPPINGRESPONSE()) {
                debuglog(string("ERROR:  s_pkt_SID_UDPPINGRESPONSE() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Sent SID_UDPPINGRESPONSE");
      
            if(! s_pkt_SID_ENTERCHAT()) {
                debuglog(string("ERROR:  s_pkt_SID_ENTERCHAT() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Sent SID_ENTERCHAT");

            if(! s_pkt_SID_JOINCHANNEL(home_channel)) {
                debuglog(string("ERROR:  s_pkt_SID_JOINCHANNEL(home_channel)")
                         + string(" broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Sent SID_JOINCHANNEL");
            qApp->processEvents();
        } break;
    


        case SID_ENTERCHAT: {
            if(! r_pkt_SID_ENTERCHAT()) {
                debuglog(string("ERROR:  r_pkt_SID_ENTERCHAT() broke.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Recv SID_ENTERCHAT");
            qApp->processEvents();
            login_complete = true;
        } break;


        case SID_NULL: {
            // do nothing.
            debuglog("Recv SID_NULL");
            qApp->processEvents();
        } break;
    
        case 0x4A: {
            // Just trash the packet and do nothing.  This is just a
            // request for user stats and comp info.  I don't (I
            // think?) need to send anything back and can ignore it.

            // *debug << "DEBUG:  got a 0x4A from the server (nothing bad)." 
            //        << endl
            //        << "          " << __FILE__ << "(" 
            //        << QString::number(int(__LINE__)).ascii() << ")" << endl;
            if(! trash_curr_packet()) {
                debuglog(string("ERROR:  Problem trashing packet 0x4A.")
                         + string("          ") + string( __FILE__ )
                         + string("(") 
                         + string( QString::number(int(__LINE__)).ascii() ) 
                         + string(")"));
                log("Connect attempt failed.");
                disconnectNow();
                return false;
            }
            debuglog("Recv 0x4A");
        } break;

        default:
            // shit! what do we do?  stuff's comin all weird, wrong or
            // out of order.
            debuglog(string("ERROR:  Unexpected packet: ")
                     + string("          ") + string( __FILE__ )
                     + string("(") 
                     + string( QString::number(int(__LINE__)).ascii() ) 
                     + string(")"));
            debuglog(recvBuff.print_buffer());
            log("Connect attempt failed.");
            disconnectNow();
            return false;
        }  // end of switch(recvBuff.getPacketID())

        recvBuff.setStripped( false );
    } // end of while(!login_complete)

    if( !qtsocket->isValid() )
    {
        log("Connect attempt failed.");
        disconnectNow();
        return false;
    }

    log("Server logon successful.   :)");
    log("Enter chat below ( \"/exit\" to exit):  ");


    log(string("Logged in as user: ") + this->username());
    emit connectedToBnet();

    return true;
}  // end of bool conGuy::connectToBnet()



// This function not only sends the sendBuff to bnet, but it also 
//   manages the delay required to avoid flooding off bnet and getting 
//   IP banned.
bool conGuy::sendBuffOut() {
    void* ourData = sendBuff.getPtr();

    // Flood protection: 
    if( (sendBuff.getPacketID() != SID_PING) && (!sleepRequiredTime()) ) {
        debuglog("ERROR: sleepRequiredTime() failed on a sendBuffOut()");
        return false;
    }
  
    if( ! writeit(ourData, sendBuff.size()) ) {
        debuglog(string("ERROR:  Can't send data:  sendBuffOut()")
                 + string("          ") + string( __FILE__ )
                 + string("(") 
                 + string( QString::number(int(__LINE__)).ascii() ) 
                 + string(")"));
        return false;
    }
    if(!updateLastSendTime()) {
        // Should this be an error or a warning?
        debuglog("ERROR:  updateLastSendTime() failed in sendBuffOut()");
        // Should I return false or true?  Technically it DID send... 
        // Eh, chances are that the update time function fails is too
        // low to care.
        return false;
    }
    return true;
}  // end of bool conGuy::sendBuffOut()




// Send protocol byte.  (game protocol)
bool conGuy::s_pkt_protocol() {
  unsigned char protocol_byte = 0x01;  // game protocol == 0x01.
  //  int rc;
  return writeit(&protocol_byte, 1 /* byte */);
}  // end of bool conGuy::s_pkt_protocol()




// Send SID_AUTH_INFO packet.  This is just basic stuff about the computer
//   running the program.
bool conGuy::s_pkt_SID_AUTH_INFO() {
  u8 packetID = (u8)SID_AUTH_INFO;
  u32 protocolID = 0;
  char platformID[5]  = "68XI";
  char programID[5]   = "PXES";
  u32 versionByte = this->getVersionByte();
  u32 productLanguage = (u32)0x00;    // unknown.  always 0.
  u32 localIP         = (u32)0x00;    // It should log on ok...
  u32 timeBias        = (u32)0x012c;  // EST.  East side baby.
  u32 localeID        = (u32)0x0409;  // english
  u32 languageID      = (u32)0x0409;  // english
  char countryAbbrev[4]  = "USA";
  char country[14]       = "United States";

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&protocolID,      sizeof(u32));
  sendBuff.add(platformID, 4);
  sendBuff.add(programID,  4);
  sendBuff.add(&versionByte,     sizeof(u32));
  sendBuff.add(&productLanguage, sizeof(u32));
  sendBuff.add(&localIP,         sizeof(u32));
  sendBuff.add(&timeBias,        sizeof(u32));
  sendBuff.add(&localeID,        sizeof(u32));
  sendBuff.add(&languageID,      sizeof(u32));
  sendBuff.add(countryAbbrev,    strlen(countryAbbrev)+1);
  sendBuff.add(country,          strlen(country)+1);
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_AUTH_INFO failed.")
    	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_AUTH_INFO failed.")
    	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();
  return true;
}  // end of bool conGuy::s_pkt_SID_AUTH_INFO()



// Receive a ping value.
bool conGuy::r_pkt_SID_PING(u32 &pingValue) {
  // Return the successfulness of extraction.
  return recvBuff.extract(pingValue);
}



// This function sends back the ping we got from r_pkt_SID_PING().
bool conGuy::s_pkt_SID_PING(u32 pingValue) {
  // Send it back.
  sendBuff.clear();
  sendBuff.setPacketID(SID_PING);
  if(! sendBuff.add(&pingValue, sizeof(u32))) {
    debuglog(string("ERROR:  in s_pkt_SID_PING(), couldn't add ")
	     + string("pingValue to sendBuff")
    	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(! sendBuff.package()) {
    debuglog(string("ERROR:  in returnPing(), couldn't package sendBuff")
    	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  return sendBuffOut();  
}  // end of bool conGuy::s_pkt_SID_PING(u32 pingValue)




// This packet gives us variables and the equation used to do the 
//   game version check.
bool conGuy::r_pkt_SID_AUTH_INFO() {
  // This is the format of the packet.
  
  //     (DWORD)		 Logon Type
  //     (DWORD)		 Server Token
  //     (DWORD)		 UDPValue**
  //     (FILETIME)	 MPQ filetime
  //     (STRING)		 IX86ver filename
  //     (STRING)		 ValueString


  u32 logonType;     // Logon Type.  (0 == Broken Sha-1 (STAR/SEXP/D2DV/D2XP)
                     //               1 == NLS version 1 (War3Beta),
                     //               2 == NLS Version 2 (WAR3))
  u32 serverToken;

  u32 udpValue;       // We won't use this.  See note below.

  // The next two variables are actually the two ints from a timeval.
  // This is extracted from the packet, but we don't use it in our
  // login process.
  u32 mpqFiletimeVal1;// We won't use this extracted value
  u32 mpqFiletimeVal2;// We won't use this extracted value.

  char hash_mpq[64];   // We extract the mpq ver from this.
  char hash_equ[128];   // This contains the equation.
  
  // Note about udpValue:
  //   No one really knows what this is, however, it is used in 2nd DWORD of 
  //   the UDP packet PKT_CONNTEST2 (0x09). It has also been seen in the realm 
  //   logon blob.  PKT_CONNTEST2 is used to establish that the client supports
  //   UDP.

  //   u32 dummy;  // dummy-value.

  // Extract the packet stuff's.
  recvBuff.extract(logonType);
  recvBuff.extract(serverToken);
  recvBuff.extract(udpValue);

  // These two 'mpqFiletimeVal's are a timeval
  recvBuff.extract(mpqFiletimeVal1);  
  recvBuff.extract(mpqFiletimeVal2);

  recvBuff.extract(hash_mpq);
  recvBuff.extract(hash_equ);
  
  // Set the values in the authGuy object.
  auth.setLogonType(logonType);
  auth.setServerToken(serverToken);
  auth.setUdpValue(udpValue);
  // auth.setMpqFiletime(mpqFiletime); // Not used, so commented out.
  auth.setHash_mpq(hash_mpq);
  auth.setHash_equ(hash_equ);
  

  return true;
}  // end of bool conGuy::r_pkt_SID_AUTH_INFO()




// This packet sends out the info concerning the game version checking.
bool conGuy::s_pkt_SID_AUTH_CHECK() {
  u32 cdkeyValue2;
  u8 packetID = SID_AUTH_CHECK;

  // Below are the elements of this packet in this order.
  u32 clientToken;
  u32 exeVer;
  u32 checkRevisionResult;
  u32 numKeys;
  u32 spawn;
  u32 keyLength;
  u32 cdkeyValue0;  // (product)
  u32 cdkeyValue1;
  u32 zero_u32 = 0x00;  // unknown field.
  u32 *hashed_data;  // This points to 5 u32's.  (20 bytes).
  string exe_info;
  string owner_name;

  // Store stor'in thingS: 
  clientToken = auth.getClientToken();
  exeVer = this->getExeVer();
  checkRevisionResult = auth.checkRevision();
  numKeys = 0x01;  // only 1 key for sc/bw.
  spawn   = 0x00;  // no spawn
  keyLength = 0x0d;  // sc/bw has key length of 13
  if(!auth.getCDVars()) {
    debuglog(string("ERROR:   getCDVars failed.")
    	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }


  cdkeyValue0 = auth.getCdkeyVar0();
  cdkeyValue1 = auth.getCdkeyVar1();
  cdkeyValue2 = auth.getCdkeyVar2();
  

  debuglog(string("Client Token:  ") + print_hex_form(clientToken));
  debuglog(string("Exe Ver:       ") + print_hex_form(exeVer));
  debuglog(string("Check Revision:") + print_hex_form(checkRevisionResult));
  debuglog(string("Num Keys:      ") + print_hex_form(numKeys));
  debuglog(string("Spawn:         ") + print_hex_form(spawn));
  debuglog(string("Key Length:    ") + print_hex_form(keyLength));

  debuglog(string("CDkey values:"));
  debuglog(print_hex_form(cdkeyValue0));
  debuglog(print_hex_form(cdkeyValue1));
  debuglog(print_hex_form(cdkeyValue2));

  // Dump cd key info.
  //   debuglog(string("CDkey values:"));
  //   string hexDump = "";
  //   for(unsigned int i = 0; i < sizeof(u32); i++) {
  //     hexDump += print_hex_form( *(((u8*)&cdkeyValue0) +i) );
  //   }
  //   debuglog(hexDump);
  //   hexDump = "";
  //   for(unsigned int i = 0; i < sizeof(u32); i++) {
  //     hexDump += print_hex_form( *(((u8*)&cdkeyValue1) +i) );
  //   }
  //   debuglog(hexDump);
  //   hexDump = "";
  //   for(unsigned int i = 0; i < sizeof(u32); i++) {
  //     hexDump += print_hex_form( *(((u8*)&cdkeyValue2) +i) );
  //   }
  //   debuglog(hexDump);
  //   hexDump = "";


  hashed_data = auth.getHashData();

  exe_info = auth.getExeInfo();
  owner_name = "GiTMchat user";

  // done setting the vars we need, now prepare the packet.
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&clientToken,         sizeof(u32));
  sendBuff.add(&exeVer,              sizeof(u32));
  sendBuff.add(&checkRevisionResult, sizeof(u32));
  sendBuff.add(&numKeys,             sizeof(u32));
  sendBuff.add(&spawn,               sizeof(u32));
  sendBuff.add(&keyLength,           sizeof(u32));
  sendBuff.add(&cdkeyValue0,         sizeof(u32));
  sendBuff.add(&cdkeyValue1,         sizeof(u32));
  sendBuff.add(&zero_u32,            sizeof(u32));
  for(int i = 0; i < 5; i++)
    sendBuff.add(&hashed_data[i],    sizeof(u32));
  sendBuff.add(exe_info.c_str(),     exe_info.length()+1);
  sendBuff.add(owner_name.c_str(),   owner_name.length()+1);


  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_AUTH_CHECK failed.")
    	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_AUTH_CHECK failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();
  

  return true;

} // end of bool conGuy::s_pkt_SID_AUTH_CHECK()




// This packet gives us the result of our game version check send.
bool conGuy::r_pkt_SID_AUTH_CHECK() {
  u32 result;  // result of the auth
  string additionalInfo;  // Additional info msg.
  
  recvBuff.extract(result);
  recvBuff.extract(additionalInfo);

  switch (result) {
  case 0x000:
    // Auth challenge passed.
    return true;
    break;
  case 0x100:
    log(string("ERROR:  Old Game Version.  Patch MPQ file: ")
	     + additionalInfo);
    return false;
    break;
  case 0x101:
    log("ERROR:  Invalid Game Version.  ");
    return false;
    break;
  case 0x200:
    log("ERROR:  Invalid CD key. ");
    return false;
    break;
  case 0x201:
    log(string("ERROR:  CD key is in use!  OMFG.  Kill this guy: ")
	+ additionalInfo);
    return false;
    break;
  case 0x202:
    log(string("ERROR:  Aww crap.  You're using a banned cd key."));
    return false;
    break;
  case 0x203:
    log(string("ERROR:  Wrong Product."));
    return false;
    break;
  default:
    log("ERROR:  Unknown response for SID_AUTH_CHECK (0x51).");
    return false;
    break;
  }
  return false;
}  // end of bool conGuy::r_pkt_SID_AUTH_CHECK()




// Send out the logon info.  This includes the hash of the password and
//   client and server tokens.  The account name is also sent as a string.
bool conGuy::s_pkt_SID_LOGONRESPONSE() {

  u32 packetID = SID_LOGONRESPONSE;
  u32 clientToken = auth.getClientToken();
  u32 serverToken = auth.getServerToken();
  u32 password_hash[5];
  string account = auth.getAccount();


  // copy the password_hash over from the authGuy object.
  memcpy(password_hash, auth.getPasswordHash(), sizeof(u32) * 5);

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&clientToken,         sizeof(u32));
  sendBuff.add(&serverToken,         sizeof(u32));
  sendBuff.add(password_hash,        sizeof(u32) * 5);
  sendBuff.add(account.c_str(),      account.length() + 1);
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_LOGONRESPONSE failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_LOGONRESPONSE failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();
  return true;
} // end of bool conGuy::s_pkt_SID_LOGONRESPONSE()




// This packet gives us the status response to our password hash send.
bool conGuy::r_pkt_SID_LOGONRESPONSE() {
  // result of the password send.
  u32 result;
  recvBuff.extract(result);
  
  if(result == 0x01) {
    // login success.
    log("Account and password logon success.");
    return true;
  }
  else if(result == 0x00) {
    // invalid password.
    log("ERROR:  Invalid password");
    return false;
  }
  else {
    // uhh.... dunno what this. 
    string logStr = "ERROR:  Unknown SID_LOGONRESPONSE from server.  ";
    logStr += "Got result == ";

    for(unsigned int i = 0; i < sizeof(u32); i++) {
      logStr += print_hex_form( *(((u8*)&result) +i) );
    }
    debuglog(logStr);
    return false;
  }
} // end of bool conGuy::r_pkt_SID_LOGONRESPONSE()




// This packet lets bnet know we can receive UDP packets.  We won't handle
//   UDP packets though.  I'm sending this because it's part of the logon
//   sequence and we can use it to set our ping to essentially 0.
bool conGuy::s_pkt_SID_UDPPINGRESPONSE() {
  u8 packetID = SID_UDPPINGRESPONSE;
  u32 udpCode;  // This is the value given to us via UDP packet 
                // PKT_SERVERPING (0x05).

                // For 0 ping, send "tenb".

  // Memcpy the DWORD(u32) of the string "bnet", which is "tenb".
  memcpy(&udpCode, "tenb", strlen("tenb"));    // (without the null)

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&udpCode,      sizeof(u32));
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_UDPPINGREPONSE failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_UDPPINGRESPONSE failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();  
  return true;
}  // end of bool conGuy::s_pkt_SID_UDPPINGRESPONSE()



// This function requests a unique username for the account so we can 
//   "enter" chat.
bool conGuy::s_pkt_SID_ENTERCHAT() {
  u8 packetID = SID_ENTERCHAT;
  string accountName = auth.getAccount();
  string statString = "";  // Just NULL for cdkey-ed products.
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(accountName.c_str(),      accountName.length()+1);
  sendBuff.add(statString.c_str(),       statString.length() +1);
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_ENTERCHAT failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_ENTERCHAT failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();  
  return true;
} // end of bool conGuy::s_pkt_SID_ENTERCHAT()




bool conGuy::r_pkt_SID_ENTERCHAT() {
  // We get a unique username here.  (which could be just the account name or
  //   the account name with #2, #3 etc.  appended to the end.


  string statstring;  // We'll not save this anywhere.
  
  recvBuff.extract(unique_username);
  recvBuff.extract(statstring);    // This is the 'record' games of the player
  recvBuff.extract(account);
  
  return true;
  
} // end of bool conGuy::r_pkt_SID_ENTERCHAT()




// Request for the available public channels
// Getting the channel list has no purpose for the bot.  At one point
//   I thought it was necessary in the login procedure.  Apparently not! :)
bool conGuy::s_pkt_SID_GETCHANNELLIST() {
  u8 packetID   = SID_GETCHANNELLIST;
  u32 productID = BROOD_WAR_PRODUCT_ID;    // #define in conGuy.h
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  sendBuff.add(&productID, sizeof(u32));
  

  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_GETCHANNELLIST failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_GETCHANNELLIST failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();    
  
  
  return true;
}  // end of bool conGuy::s_pkt_SID_GETCHANNELLIST()



// Receive a whole bunch of channel names that are available.
bool conGuy::r_pkt_SID_GETCHANNELLIST() {
  // This packet has a list of null-terminated strings
  //   (actually they are UTF-8, but I parse them as ascii)
  //   which are terminated by a empty string.
  //   I'm just throwing them away.  
  
  
//   *debug << "DEBUG:  This is what our r_pkt_SID_GETCHANNELLIST looks like:"
// 	 << endl << endl;
  recvBuff.print_buffer();
//   *debug << endl;
  
  string aChannel;
  recvBuff.extract(aChannel);
  while(aChannel.length() != 0) {
    recvBuff.extract(aChannel);
  }
  
  return true;
} // end of bool conGuy::r_pkt_SID_GETCHANNELLIST()



// Send some text just like you would in the actual brood war game.
// This includes /join 's and such.
bool conGuy::s_pkt_SID_CHATCOMMAND(string msg) {
  u8 packetID = SID_CHATCOMMAND;
  string command = msg;

  sendBuff.clear();
  sendBuff.setPacketID(packetID);
  

  sendBuff.add(command.c_str(), command.length()+1);

  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_CHATCOMMAND failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_CHATCOMMAND failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(") 
             + string( QString::number(int(__LINE__)).ascii() ) 
             + string(")"));
    return false;
  }
  sendBuff.clear();    

  return true;
} // end of bool conGuy::s_pkt_SID_CHATCOMMAND(string msg)



// I use this only in the beginning to join a channel opon login.
//   I use this because I suppose it's a request to join a channel before
//   we have officially "entered chat".  After we've offically "entered chat"
//   I join channels by just plain sending the "/join <channel>" as a 
//   chat command/message..
bool conGuy::s_pkt_SID_JOINCHANNEL(string channel) {

  u8 packetID = SID_JOINCHANNEL;
  u32 flags = 0x02;
  string toJoin = channel;

  // Join types (flags value):						
  // 0x00 - Standard request to join; bnet will notify you if the channel
  //   is full or restricted.						
  // 0x01 - Join product-specific channel. W3 clients on accounts in a 	 
  //   clan will end up in their Clan channel.				
  // 0x02 - Create channel. Bnet will ipban if the channel is restricted,  
  //   but this can be used to join full channels.			 

  // From my packet logs, it seems as if 0x02 joins existing channels as well.
  //   Dunno... I'll just always send 0x02 as my flag.  We can fix this
  //   later as it works as just the initial log in send.

  sendBuff.clear();
  sendBuff.setPacketID(packetID);

  sendBuff.add(&flags, sizeof(u32));
  sendBuff.add(toJoin.c_str(), toJoin.length()+1);
  
  // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_JOINCHANNEL failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
+ string( QString::number(int(__LINE__)).ascii() )
+ string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_JOINCHANNEL failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
+ string( QString::number(int(__LINE__)).ascii() )
+ string(")"));
    return false;
  }
  sendBuff.clear();    
  return true;
}  // end of bool conGuy::s_pkt_SID_JOINCHANNEL(string channel)



// This is a blank packet just to help keep the connection alive.
bool conGuy::s_pkt_SID_NULL() {
  u8 packetID = SID_NULL;
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
    // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_NULL failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
+ string( QString::number(int(__LINE__)).ascii() )
+ string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_NULL failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
+ string( QString::number(int(__LINE__)).ascii() )
+ string(")"));
    return false;
  }
  sendBuff.clear();    
  return true;
}  // end of bool conGuy::s_pkt_SID_NULL()



// This is sent right before disconnecting.  Leaves 'chat'.
bool conGuy::s_pkt_SID_LEAVECHAT() {
  u8 packetID = SID_LEAVECHAT;
  
  sendBuff.clear();
  sendBuff.setPacketID(packetID);
    // Package and send the packet off.
  if(!sendBuff.package()) {
    debuglog(string("ERROR:  packaging for packet SID_LEAVECHAT failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
+ string( QString::number(int(__LINE__)).ascii() )
+ string(")"));
    return false;
  }
  if(!sendBuffOut()) {
    debuglog(string("ERROR:  send for packet SID_LEAVECHAT failed.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
+ string( QString::number(int(__LINE__)).ascii() )
+ string(")"));
    return false;
  }
  sendBuff.clear();    
  return true;
}  // end of bool conGuy::s_pkt_SID_LEAVECHAT()



bool conGuy::r_pkt_SID_FLOODDETECTED() {
  // This packet has no data.
  return true;
}


bool conGuy::r_pkt_SID_MESSAGEBOX() {
  u32 style;
  string caption;
  string text;
  
  recvBuff.extract(style);
  recvBuff.extract(caption);
  recvBuff.extract(text);


  QMessageBox msgbox(caption.c_str(), text.c_str(), QMessageBox::Information,
		     (QMessageBox::Ok), (Qt::NoButton), 
		     (Qt::NoButton));

  msgbox.exec();
  return true;
}


// Returns an BnetEvent object of the next event in the current channel.
BnetEvent conGuy::gimmeEvent() {
  BnetEvent newEvent;   // <-- return value.

  if( !qtsocket->isValid() )
  {
      newEvent.exit_now = true;
      return newEvent;
  }

  int timeout = 50;
  if( !readFullPacket( timeout ) )
  {
      // Timeout occurred.  Nothing to read in.  (Hopefully it wasn't
      // an error).

      // Aquire a lock on the mutex for our sendQueue, then send
      //   an item in it if it has anything in it.
      //      int rc;
      toSend.mutex.lock();
      if(toSend.out.size() > 0) {
	  // Goodie.  We have some treats for us!
	  string msg = toSend.out.front();
	  toSend.out.pop_front();
	  
	  if(! say(msg)) {
              log("ERROR:  Send message failed for packet SID_CHATCOMMAND.");
              toSend.mutex.unlock();
              newEvent.exit_now = true;
              return newEvent;
	  }
      }
      toSend.mutex.unlock();


      // Ask the calling object to try to get an event again because
      //   this event has no useful infomation in it.
      newEvent.exit_now = false;
      newEvent.try_again = true;
      return newEvent;
  }
  else
  {
      // We read something into our buffer.

      // Let's analyze this packet.
      //   char pktID[16];
      //   sprintf(pktID, "%d", recvBuff.getPacketID());
      //   debuglog(string("Processing input packet ") + string(pktID));

      // The 'event' switch.
      switch(recvBuff.getPacketID()) {
      case SID_CHATEVENT:
    
          //debuglog("Recv SID_CHATEVENT.");
          //recvBuff.print_buffer();

          // Format:
          // (u32) Event ID
          // (u32) Flags
          // (u32) Ping
          // (u32) IP Address*
          // (u32) Account number*
          // (u32) Registration authority*
          // (STRING) Username
          // (STRING) Text
    
          // * == defunct.
          u32 defunct_value;  // This will get thrown away.

          recvBuff.extract(newEvent.eventID);
          recvBuff.extract(newEvent.flags);
          recvBuff.extract(newEvent.ping);
          recvBuff.extract(defunct_value);  
          recvBuff.extract(defunct_value);
          recvBuff.extract(defunct_value);
          recvBuff.extract(newEvent.username);
          recvBuff.extract(newEvent.text);
    
          break;
      default:
          // Handle a non-chatevent.
          //debuglog("DEBUG:  Handling nonchat packet...");
          if(! handle_nonchat()) {
              disconnectNow();
              newEvent.exit_now = true;
          }
          else {
              // Try to get another event...
              newEvent.exit_now = false;
              newEvent.try_again = true;
          }
          break;
      }
      recvBuff.setStripped( false );
      return newEvent;
  }
}

bool conGuy::handle_nonchat() {

  switch(recvBuff.getPacketID()) {

  case SID_FLOODDETECTED:
    // Server will disconnect us right after sending this message.
    log("Flood detected by server.");

    // r_pkt_SID_FLOODDETECTED() does absolutely nothing!!
    //   It's just there for consistency.
    if(! r_pkt_SID_FLOODDETECTED()) {
      debuglog("ERROR:  r_pkt_SID_FLOODDETECTED() failed.");
      return false;
    }
    //debuglog("Recv SID_FLOODDETECTED.");
    // false means quit in this case.
    return false;
    break;


  case SID_MESSAGEBOX:

    if(! r_pkt_SID_MESSAGEBOX()) {
      debuglog("ERROR:  r_pkt_SID_MESSAGEBOX() failed.");
      return false;
    }
    //debuglog("Recv SID_MESSAGEBOX.");
    return true;
    break;


  case SID_PING:
    u32 pingVal;

    if(! r_pkt_SID_PING(pingVal)) {
      debuglog("ERROR:  r_pkt_SID_PING() failed.");
      return false;
    }
    //debuglog("Recv SID_PING");

    if(! s_pkt_SID_PING(pingVal)) {
      debuglog("ERROR:  s_pkt_SID_PING() failed.");
      return false;
    }
    //debuglog("Sent SID_PING");
    return true;
    break;


    // TODO:  These should probably be put in the event switch.
    // Search for "The 'event' switch." to see which one I mean.
  case SID_FRIENDLIST:
  case SID_FRIENDUPDATE:
  case SID_FRIENDADDED:
  case SID_FRIENDREMOVED:
  case SID_FRIENDMOVED:


  case SID_NULL:
  default:
    return trash_curr_packet();
  }  // End of switch(recvBuff.getPacketID())

}

// Trash the current stripped packet in the buffer.  NOT clear the buffer!
bool conGuy::trash_curr_packet() {
  
  unsigned short size;
  size = recvBuff.getPacketSz();

  //   *debug << "DEBUG:  About to trash pkt "
  // 	 << print_hex_form(recvBuff.getPacketID())
  // 	 << ".  Here it is before "
  // 	 << "stripping and trashing:" << endl;
  
  //   *debug << "0xFF "                                   // Initial byte
  // 	 << print_hex_form(recvBuff.getPacketID())    // Packet ID
  // 	 << print_hex_form( * (((u8*)&size)+0) )  // Size (1st byte)
  // 	 << print_hex_form( * (((u8*)&size)+1) ); // Size (2nd byte)
  for(int i = 0; i < size; i++) {
    u8 currByte;
    recvBuff.extract(currByte);
    //     *debug << print_hex_form( currByte );
  }
  //   *debug << endl;

  //   *debug << "DEBUG:  Finished trashing packet " 
  // 	 << print_hex_form(recvBuff.getPacketID()) << endl;


  if(recvBuff.getPacketSz() != 0) {
    debuglog(string("ERROR:  This packet shoulda got trashed but ")
	     + string("packetSz isn't 0.")
	     + string("          ") + string( __FILE__ )
	     + string("(")
             + string( QString::number(int(__LINE__)).ascii() )
             + string(")"));
    return false;
  }
  return true;
}

string to_s(u32 num) {
  char temp[16];
  sprintf(temp, "%d", num);
  return string(temp);
}

string conGuy::printErrorType( Q3SocketDevice::Error err )
{
    switch( err )
    {
    case Q3SocketDevice::NoError:
        return "QSocketDevice::NoError";
    case Q3SocketDevice::AlreadyBound:
        return "QSocketDevice::AlreadyBound";
    case Q3SocketDevice::Inaccessible:
        return "QSocketDevice::Inaccessible";
    case Q3SocketDevice::NoResources:
        return "QSocketDevice::NoResources";
    case Q3SocketDevice::InternalError:
        return "QSocketDevice::InternalError";
    case Q3SocketDevice::Impossible:
        return "QSocketDevice::Impossible";
    case Q3SocketDevice::NoFiles:
        return "QSocketDevice::NoFiles";
    case Q3SocketDevice::ConnectionRefused:
        return "QSocketDevice::ConnectionRefused";
    case Q3SocketDevice::NetworkFailure:
        return "QSocketDevice::NetworkFailure";
    case Q3SocketDevice::UnknownError:
        return "QSocketDevice::UnknownError";
    default:
        return "Unknown QSocketDevice Error";
    }
}


// precondition: recvBuff has either already stripped the header bytes
// from a previous recv or it has not.  Most of the time, it will
// still need to strip the header bytes.  If it needs to strip the
// header bytes, then it goes into the first section where it tries to
// read enough for the header.  If it doesn't need to strip the
// header, it checks to make sure there are enough bytes in the
// buffer.  If not, it tries to read more.

// postcondition: recvBuff will contain bytes which holds at least a
// full packet.  (possible to have more bytes after that too).  The
// recvBuff will have already stripped the header (recvBuff.stripped()
// == true) and have the the size() and packetSz() set accordingly.

// timeout is in milliseconds.

// NOTE: Anyone who finishes extracting the packet must reset
// recvBuff.stripped() to false.  That way this function knows it
// should try to read another full packet.
bool conGuy::readFullPacket( const int& timeout )
{
    //    debuglog("DEBUG:  reading full packet...");
    //    qApp->processEvents();
    int rc;
    unsigned char conGuyBuff[MAX_MSG];
    bool timedOut;
    Q_LONG bytesAvail = 0;

    if( !recvBuff.stripped() )
    {
        if( recvBuff.size() < 4 )
        {
            bytesAvail = qtsocket->waitForMore( timeout, &timedOut );
            if( bytesAvail == -1 )
            {
                // Socket error.
                log( string("ERROR:  ") + 
                     printErrorType( qtsocket->error() ) );
                disconnectNow();
                return false;
            }
            else if( bytesAvail == 0 && timedOut == false )
            {
                log( "Server closed the connection." );
                disconnectNow();
                return false;
            }
            else if( timedOut == true )
            {
                // Timed out.
                // log("Connection timed out waiting for data.");
                return false;
            }

            // Great!  Let's start reading in data now.

            rc = readit(conGuyBuff, MAX_MSG);
            if(rc < 0) {
                debuglog(string("ERROR:  Can't readit data.  ")
                         + string("          ") + string( __FILE__ )
                         + string("(")
                         + string(QString::number(int(__LINE__)).ascii()) 
                         + string(")"));
                qApp->processEvents();
                return false;
            }

            //         char rcStr[16];
            //         sprintf(rcStr, "%d", rc);
            //         debuglog(string("Got a packet from the server. rc == ")
            //                  + string(rcStr));
            //         qApp->processEvents();

            recvBuff.add(conGuyBuff, rc);
        }

        // Strip the header.
        if( !recvBuff.strip() )
        {
            debuglog( "Strip failed!." );
            qApp->processEvents();
            disconnectNow();
            return false;
        }
    }
    recvBuff.setStripped( true );
    
    //         debuglog( string("got packet id == ") + 
    //                   QString::number(recvBuff.getPacketID()).ascii() );
    //         debuglog( string("got packet Sz == ") +
    //                   QString::number(recvBuff.getPacketSz()).ascii() );
    //         debuglog( string("recvBuff size == ") + 
    //                   QString::number(recvBuff.size()).ascii() );
    //         qApp->processEvents();

    // Check for incomplete packet.
    if( recvBuff.size() < recvBuff.getPacketSz() )
    {
        //         debuglog("incomplete packet size");
        //         qApp->processEvents();
        bytesAvail = qtsocket->bytesAvailable();

        // Need to read some more of the packet off the socket.
        Q_LONG toRead = recvBuff.getPacketSz() - recvBuff.size();
        while( bytesAvail < toRead )
        {
            bytesAvail = qtsocket->waitForMore(timeout, &timedOut);
            if( bytesAvail == -1 )
            {
                // Socket error.
                log( string("ERROR:  waitForMore():  ") + 
                     printErrorType( qtsocket->error() ) );
                disconnectNow();
                return false;
            }
            else if( timedOut == true )
            {
                // Timed out.
                log( string("Connection timed out waiting for the rest ") +
                     string("of the packet data.") );
                return false;
            }
        }

        // the qsocketdevice says we have enough waiting for what we
        // wanted.  read it.
        rc = readit(conGuyBuff, MAX_MSG);
        if(rc < 0) {
            debuglog(string("ERROR:  Can't readit data.  ")
                     + string("          ") + string( __FILE__ )
                     + string("(")
                     + string(QString::number(int(__LINE__)).ascii()) 
                     + string(")"));
            return false;
        }

        //         char rcStr[16];
        //         sprintf(rcStr, "%d", rc);
        //         debuglog(string("Got a packet from the server. rc == ")
        //                  + string(rcStr));

        recvBuff.add(conGuyBuff, rc);
    }

    //    debuglog("returning true");
    //    qApp->processEvents();    
    return true;
}

